﻿#region Using directives

using System;
using System.CodeDom;
using System.Collections.Generic;

using Weazel.Gezel.Model;
using Weazel.Gezel.Conversions.Model;

#endregion

namespace Weazel.Gezel.Conversions
{
  public class ExpressionConversionHelper
  {
    public static CodeFieldReferenceExpression BuildEvaluateMethodRecursive(
      Expression expr, 
      CodeStatementCollection statements,
      Weazel.Gezel.Conversions.Model.Type type)
    {
      if (expr is LeafExpression)
      {
        return processLeafExpression(expr, statements, type);
      }
      else if (expr is UnaryExpression)
      {
        return processUnaryExpression(expr, statements, type);
      }
      else if (expr is RangeExpression)
      {
        return processRangeExpression(expr, statements, type);
      }
      else if (expr is BinaryExpression)
      {
        return processBinaryExpression(expr, statements, type);
      }
      else if (expr is TernaryExpression)
      {
        return processTernaryExpression(expr, statements, type);
      }

      throw new NotImplementedException();
    }

    private static CodeFieldReferenceExpression processRangeExpression(
      Expression expr, 
      CodeStatementCollection statements, 
      Weazel.Gezel.Conversions.Model.Type type
      )
    {
      RangeExpression rExpr = expr as RangeExpression;
      CodeFieldReferenceExpression childIdentifier = BuildEvaluateMethodRecursive(rExpr.Child, statements, type);

      string generatedIdentifier = string.Format("Value_{0}", expr.Name.Replace(".", "_"));
      IntegerField generatedInteger = type.AddIntegerField(generatedIdentifier, expr.Width, expr.Signed, "0");
      generatedInteger.Comments.Add(new CodeCommentStatement(string.Format("Used to evaluate expression: {0}", expr.ToString())));

      CodeFieldReferenceExpression fieldReference = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), generatedIdentifier);

      //if (rExpr.From != rExpr.To)
      //{
        CodeMethodReferenceExpression getRangeMethod = 
          new CodeMethodReferenceExpression(new CodeTypeReferenceExpression("Integer"), "GetRange");

        statements.Add(
          new CodeMethodInvokeExpression(
            getRangeMethod, 
            new CodeDirectionExpression(FieldDirection.Ref, fieldReference), 
            childIdentifier, 
            new CodePrimitiveExpression((int)rExpr.From), 
            new CodePrimitiveExpression((int)rExpr.To)
          )
        );
      //}
      //else
      //{
      //  CodeMethodReferenceExpression getBitMethod = 
      //    new CodeMethodReferenceExpression(new CodeTypeReferenceExpression("Integer"), "GetBit");
        
      //  statements.Add(
      //    new CodeMethodInvokeExpression(
      //      getBitMethod,
      //      new CodeDirectionExpression(FieldDirection.Ref, fieldReference),
      //      childIdentifier,
      //      new CodePrimitiveExpression((int)rExpr.From)
      //    )
      //  );
      //}
      return fieldReference;
    }

    private static CodeFieldReferenceExpression processUnaryExpression(
      Expression expr, 
      CodeStatementCollection statements, 
      Weazel.Gezel.Conversions.Model.Type type
      )
    {
      UnaryExpression uExpr = expr as UnaryExpression;
      CodeFieldReferenceExpression childIdentifier = BuildEvaluateMethodRecursive(uExpr.Child, statements, type);

      string generatedIdentifier = string.Format("Value_{0}", expr.Name.Replace(".", "_"));
      IntegerField generatedInteger = type.AddIntegerField(generatedIdentifier, expr.Width, expr.Signed, "0");
      generatedInteger.Comments.Add(new CodeCommentStatement(string.Format("Used to evaluate expression: {0}", expr.ToString())));

      CodeFieldReferenceExpression fieldReference = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), generatedIdentifier);
      if (expr.ExpressionType == ExpressionType.UnarySub)
      {
        //statements.Add(new CodeSnippetStatement(string.Format("Integer.Negate(ref {0}, {1});", generatedIdentifier, childIdentifier)));
        CodeMethodReferenceExpression methodReference = new CodeMethodReferenceExpression(new CodeTypeReferenceExpression("Integer"), "Negate");
        statements.Add(new CodeMethodInvokeExpression(methodReference, new CodeDirectionExpression(FieldDirection.Ref, fieldReference), childIdentifier));
      }
      else if (expr.ExpressionType == ExpressionType.Not)
      {
        //statements.Add(new CodeSnippetStatement(string.Format("Integer.Not(ref {0}, {1});", generatedIdentifier, childIdentifier)));
        CodeMethodReferenceExpression methodReference = new CodeMethodReferenceExpression(new CodeTypeReferenceExpression("Integer"), "Not");
        statements.Add(new CodeMethodInvokeExpression(methodReference, new CodeDirectionExpression(FieldDirection.Ref, fieldReference), childIdentifier));
      }
      else
        throw new System.NotImplementedException();

      return fieldReference;
    }

    private static CodeFieldReferenceExpression processLeafExpression(
      Expression expr, 
      CodeStatementCollection statements, 
      Weazel.Gezel.Conversions.Model.Type type
      )
    {
      LeafExpression lExpr = expr as LeafExpression;
      if (lExpr.ExpressionType == ExpressionType.Constant)
      {
        string generatedIdentifier = string.Format("Constant_{0}", lExpr.Value.Replace("-", "minus_"));
        CodeFieldReferenceExpression fieldReference =
          new CodeFieldReferenceExpression(
            new CodeThisReferenceExpression(),
            generatedIdentifier
          );

        IntegerField generatedInteger = type.AddIntegerField(generatedIdentifier, lExpr.Width, lExpr.Signed, lExpr.Value);
        generatedInteger.Comments.Add(new CodeCommentStatement(string.Format("The constant value {0}", lExpr.Value)));

        return fieldReference;
      }
      else if (lExpr.ReferencedEntity is Register)
      {
        string registerIdentifier = string.Format("register_{0}", lExpr.ReferencedEntity.Name.Replace(".", "_"));

        CodeFieldReferenceExpression fieldReference =
          new CodeFieldReferenceExpression(
            new CodeFieldReferenceExpression(
              new CodeTypeReferenceExpression("Simulator"),
              registerIdentifier
            ),
            "StoredValue"
          );
        return fieldReference;
      }
      else if (lExpr.ReferencedEntity is OutPort)
      {
        string typeName = string.Format("outPort_{0}", lExpr.ReferencedEntity.Name.Replace(".", "_"));
        //statements.Add(new CodeSnippetStatement(string.Format("System.Diagnostics.Debug.Assert(Simulator.outPort_{0}.Cycle == Simulator.CurrentCycle);",typeName)));
        CodeTypeReferenceExpression simulatorTypeReference = new CodeTypeReferenceExpression("Simulator");
        CodeExpression argument = new CodeBinaryOperatorExpression(
          new CodeFieldReferenceExpression(new CodeFieldReferenceExpression(simulatorTypeReference, typeName), "Cycle"),
          CodeBinaryOperatorType.IdentityEquality,
          new CodeFieldReferenceExpression(simulatorTypeReference, "CurrentCycle")
        );

        CodeMethodReferenceExpression method =
          new CodeMethodReferenceExpression(
            new CodeTypeReferenceExpression("Debug"),
            "Assert"
          );

        statements.Add(new CodeMethodInvokeExpression(method, argument));

        CodeFieldReferenceExpression fieldReference =
          new CodeFieldReferenceExpression(
            new CodeFieldReferenceExpression(
              new CodeTypeReferenceExpression("Simulator"),
              typeName
            ),
            "Value"
          );
        return fieldReference;
      }
      else if (lExpr.ReferencedEntity is InPort)
      {
        InPort inport = lExpr.ReferencedEntity as InPort;
        if (inport.Parent is Datapath)
        {
          //Console.WriteLine("using portmap with inport {0}", inport);
         
          
          
          string outPortOrSignalInstanceName;

          if (type.SoftwareModel.PortMappings.ContainsInport(inport))
          {
            OutPort outport = type.SoftwareModel.PortMappings[inport];
            outPortOrSignalInstanceName = string.Format("outPort_{0}", outport.Name.Replace(".", "_"));
          }
          else
          {
            System.Diagnostics.Trace.Assert(type.SoftwareModel.SignalMappings.ContainsInport(inport));
            Signal signal = type.SoftwareModel.SignalMappings[inport];
            outPortOrSignalInstanceName = string.Format("signal_{0}", signal.Name.Replace(".", "_"));

            // trigger evaluation of the signal
            {
              CodeFieldReferenceExpression signalReference =
                new CodeFieldReferenceExpression(
                  new CodeTypeReferenceExpression("Simulator"),
                  outPortOrSignalInstanceName
                );
              CodeMethodReferenceExpression method = new CodeMethodReferenceExpression(signalReference, "Evaluate");
              statements.Add(new CodeMethodInvokeExpression(method));
            }
          }

          // add assert statement
          {
            CodeTypeReferenceExpression simulatorTypeReference = new CodeTypeReferenceExpression("Simulator");
            CodeExpression argument = new CodeBinaryOperatorExpression(
              new CodeFieldReferenceExpression(new CodeFieldReferenceExpression(simulatorTypeReference, outPortOrSignalInstanceName), "Cycle"),
              CodeBinaryOperatorType.IdentityEquality,
              new CodeFieldReferenceExpression(simulatorTypeReference, "CurrentCycle")
            );
            CodeMethodReferenceExpression method = new CodeMethodReferenceExpression(new CodeTypeReferenceExpression("Debug"), "Assert");
            statements.Add(new CodeMethodInvokeExpression(method, argument));
          }


          CodeFieldReferenceExpression fieldReference = new CodeFieldReferenceExpression(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Simulator"), outPortOrSignalInstanceName), "Value");
          return fieldReference;
        }
        else
        {
          string inportInstanceName = string.Format("inPort_{0}", inport.Name.Replace(".", "_"));
          CodeFieldReferenceExpression fieldReference =
            new CodeFieldReferenceExpression(
              new CodeFieldReferenceExpression(
                new CodeTypeReferenceExpression("Simulator"),
                inportInstanceName
              ),
              "Value"
            );
          return fieldReference;
        }
      }
      else if (lExpr.ReferencedEntity is Signal)
      {
        // construct name of the signal
        string signalTypeName = string.Format("signal_{0}", lExpr.ReferencedEntity.Name.Replace(".", "_"));

        // trigger evaluation of the signal
        {
          CodeFieldReferenceExpression signalReference =
            new CodeFieldReferenceExpression(
              new CodeTypeReferenceExpression("Simulator"),
              signalTypeName
            );
          CodeMethodReferenceExpression method = new CodeMethodReferenceExpression(signalReference, "Evaluate");
          statements.Add(new CodeMethodInvokeExpression(method));
        }

        CodeFieldReferenceExpression fieldReference =
          new CodeFieldReferenceExpression(
            new CodeFieldReferenceExpression(
              new CodeTypeReferenceExpression("Simulator"),
              signalTypeName
            ),
            "Value"
          );
        return fieldReference;
      }
      else if(lExpr.ReferencedEntity is LeafExpression) // a constant reference
      {
        LeafExpression rExpr = lExpr.ReferencedEntity as LeafExpression;
        
        System.Diagnostics.Trace.Assert(lExpr.ExpressionType == ExpressionType.Identifier);
        System.Diagnostics.Trace.Assert(rExpr.ExpressionType == ExpressionType.Constant);

        string generatedIdentifier = string.Format("Constant_{0}", lExpr.Value.Replace("-", "minus_"));
        CodeFieldReferenceExpression fieldReference =
          new CodeFieldReferenceExpression(
            new CodeThisReferenceExpression(),
            generatedIdentifier
          );

        IntegerField generatedInteger = type.AddIntegerField(generatedIdentifier, rExpr.Width, rExpr.Signed, rExpr.Value);
        generatedInteger.Comments.Add(new CodeCommentStatement(string.Format("The constant value {0}", lExpr.Value)));

        return fieldReference;
      }

      throw new System.NotImplementedException();
    }

    private static CodeFieldReferenceExpression processTernaryExpression(
      Expression expr, 
      CodeStatementCollection statements, 
      Weazel.Gezel.Conversions.Model.Type type
      )
    {
      TernaryExpression tExpr = expr as TernaryExpression;
      CodeFieldReferenceExpression conditionIdentifier = BuildEvaluateMethodRecursive(tExpr.Condition, statements, type);
      CodeFieldReferenceExpression lhsIdentifier = BuildEvaluateMethodRecursive(tExpr.LeftChild, statements, type);
      CodeFieldReferenceExpression rhsIdentifier = BuildEvaluateMethodRecursive(tExpr.RightChild, statements, type);

      string generatedIdentifier = string.Format("Value_{0}", expr.Name.Replace(".", "_"));
      IntegerField generatedInteger = type.AddIntegerField(generatedIdentifier, expr.Width, expr.Signed, "0");
      generatedInteger.Comments.Add(new CodeCommentStatement(string.Format("Used to evaluate expression: {0}", expr.ToString())));

      CodeFieldReferenceExpression fieldReference = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), generatedIdentifier);

      CodeConditionStatement conditionStatement = new CodeConditionStatement();
      if (type.SoftwareModel.Language != TargetLanguage.IsoCpp)
      {

        CodeBinaryOperatorExpression conditionExpression =
          new CodeBinaryOperatorExpression(
            new CodeFieldReferenceExpression(
              new CodeTypeReferenceExpression("Integer"),
              "Zero"
            ),
            CodeBinaryOperatorType.IdentityInequality,
            conditionIdentifier
          );
        conditionStatement.Condition = conditionExpression;
      }
      else // C++ special case
      {
        CodeFieldReferenceExpression zeroRef =
          new CodeFieldReferenceExpression(
              new CodeTypeReferenceExpression("Integer"),
              "Zero"
            );

        CodeMethodInvokeExpression methodRef =
          new CodeMethodInvokeExpression(
            zeroRef,
            "NotEquals",
            conditionIdentifier
          );

        conditionStatement.Condition = methodRef;
      }

      conditionStatement.TrueStatements.Add(new CodeMethodInvokeExpression(fieldReference, "ValueCopy", lhsIdentifier));
      conditionStatement.FalseStatements.Add(new CodeMethodInvokeExpression(fieldReference, "ValueCopy", rhsIdentifier));

      statements.Add(conditionStatement);

      return fieldReference;
    }

    private static CodeFieldReferenceExpression processBinaryExpression(
      Expression expr, 
      CodeStatementCollection statements, 
      Weazel.Gezel.Conversions.Model.Type type
      )
    {
      BinaryExpression bExpr = expr as BinaryExpression;

      CodeFieldReferenceExpression lhsIdentifier = BuildEvaluateMethodRecursive(bExpr.LeftChild, statements, type);
      CodeFieldReferenceExpression rhsIdentifier = BuildEvaluateMethodRecursive(bExpr.RightChild, statements, type);

      string generatedIdentifier = string.Format("Value_{0}", expr.Name.Replace(".", "_"));
      IntegerField generatedInteger = type.AddIntegerField(generatedIdentifier, expr.Width, expr.Signed, "0");
      generatedInteger.Comments.Add(new CodeCommentStatement(string.Format("Used to evaluate expression: {0}", expr.ToString())));

      CodeFieldReferenceExpression fieldReference = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), generatedIdentifier);
      CodeTypeReferenceExpression integerTypeReference = new CodeTypeReferenceExpression("Integer");

      CodeFieldReferenceExpression integerZeroReference = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Integer"), "Zero");
      CodeFieldReferenceExpression integerOneReference = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Integer"), "One");

      CodeConditionStatement conditionStatement;

      switch (bExpr.ExpressionType)
      {
        case ExpressionType.Add:
          statements.Add(new CodeMethodInvokeExpression(integerTypeReference, "Add", new CodeDirectionExpression(FieldDirection.Ref, fieldReference), lhsIdentifier, rhsIdentifier));
          break;
        case ExpressionType.Sub:
          statements.Add(new CodeMethodInvokeExpression(integerTypeReference, "Subtract", new CodeDirectionExpression(FieldDirection.Ref, fieldReference), lhsIdentifier, rhsIdentifier));
          break;
        case ExpressionType.Mul:
          statements.Add(new CodeMethodInvokeExpression(integerTypeReference, "Multiply", new CodeDirectionExpression(FieldDirection.Ref, fieldReference), lhsIdentifier, rhsIdentifier));
          break;
        case ExpressionType.Div:
          statements.Add(new CodeMethodInvokeExpression(integerTypeReference, "Divide", new CodeDirectionExpression(FieldDirection.Ref, fieldReference), lhsIdentifier, rhsIdentifier));
          break;
        case ExpressionType.Mod:
          statements.Add(new CodeMethodInvokeExpression(integerTypeReference, "Modulo", new CodeDirectionExpression(FieldDirection.Ref, fieldReference), lhsIdentifier, rhsIdentifier));
          break;
        case ExpressionType.ShiftLeft:
          statements.Add(new CodeMethodInvokeExpression(integerTypeReference, "ShiftLeft", new CodeDirectionExpression(FieldDirection.Ref, fieldReference), lhsIdentifier, new CodeMethodInvokeExpression(rhsIdentifier, "ToInt")));
          break;
        case ExpressionType.ShiftRight:
          statements.Add(new CodeMethodInvokeExpression(integerTypeReference, "ShiftRight", new CodeDirectionExpression(FieldDirection.Ref, fieldReference), lhsIdentifier, new CodeMethodInvokeExpression(rhsIdentifier, "ToInt")));
          break;
        case ExpressionType.And:
          statements.Add(new CodeMethodInvokeExpression(integerTypeReference, "And", new CodeDirectionExpression(FieldDirection.Ref, fieldReference), lhsIdentifier, rhsIdentifier));
          break;
        case ExpressionType.Xor:
          statements.Add(new CodeMethodInvokeExpression(integerTypeReference, "Xor", new CodeDirectionExpression(FieldDirection.Ref, fieldReference), lhsIdentifier, rhsIdentifier));
          break;
        case ExpressionType.Ior:
          statements.Add(new CodeMethodInvokeExpression(integerTypeReference, "Or", new CodeDirectionExpression(FieldDirection.Ref, fieldReference), lhsIdentifier, rhsIdentifier));
          break;
        case ExpressionType.Lt:
          conditionStatement = new CodeConditionStatement();
          if (type.SoftwareModel.Language != TargetLanguage.IsoCpp)
          {
            conditionStatement.Condition = new CodeBinaryOperatorExpression(lhsIdentifier, CodeBinaryOperatorType.LessThan, rhsIdentifier);
          }
          else // C++ special case
          {
            conditionStatement.Condition = new CodeMethodInvokeExpression(lhsIdentifier, "LessThan", rhsIdentifier);
          }
          conditionStatement.TrueStatements.Add(new CodeAssignStatement(fieldReference, integerOneReference));
          conditionStatement.FalseStatements.Add(new CodeAssignStatement(fieldReference, integerZeroReference));
          statements.Add(conditionStatement);
          break;
        case ExpressionType.Lte:
          conditionStatement = new CodeConditionStatement();
          if (type.SoftwareModel.Language != TargetLanguage.IsoCpp)
          {
            conditionStatement.Condition = new CodeBinaryOperatorExpression(lhsIdentifier, CodeBinaryOperatorType.LessThanOrEqual, rhsIdentifier);
          }
          else // C++ special case
          {
            conditionStatement.Condition = new CodeMethodInvokeExpression(lhsIdentifier, "LessThanOrEquals", rhsIdentifier);
          }
          conditionStatement.TrueStatements.Add(new CodeAssignStatement(fieldReference, integerOneReference));
          conditionStatement.FalseStatements.Add(new CodeAssignStatement(fieldReference, integerZeroReference));
          statements.Add(conditionStatement);
          break;
        case ExpressionType.Gt:
          conditionStatement = new CodeConditionStatement();
          if (type.SoftwareModel.Language != TargetLanguage.IsoCpp)
          {
            conditionStatement.Condition = new CodeBinaryOperatorExpression(lhsIdentifier, CodeBinaryOperatorType.GreaterThan, rhsIdentifier);
          }
          else // C++ special case
          {
            conditionStatement.Condition = new CodeMethodInvokeExpression(lhsIdentifier, "GreaterThan", rhsIdentifier);
          }
          conditionStatement.TrueStatements.Add(new CodeAssignStatement(fieldReference, integerOneReference));
          conditionStatement.FalseStatements.Add(new CodeAssignStatement(fieldReference, integerZeroReference));
          statements.Add(conditionStatement);
          break;
        case ExpressionType.Gte:
          conditionStatement = new CodeConditionStatement();
          if (type.SoftwareModel.Language != TargetLanguage.IsoCpp)
          {
            conditionStatement.Condition = new CodeBinaryOperatorExpression(lhsIdentifier, CodeBinaryOperatorType.GreaterThanOrEqual, rhsIdentifier);
          }
          else // C++ special case
          {
            conditionStatement.Condition = new CodeMethodInvokeExpression(lhsIdentifier, "GreaterThanOrEquals", rhsIdentifier);
          }
          conditionStatement.TrueStatements.Add(new CodeAssignStatement(fieldReference, integerOneReference));
          conditionStatement.FalseStatements.Add(new CodeAssignStatement(fieldReference, integerZeroReference));
          statements.Add(conditionStatement);
          break;
        case ExpressionType.Eq:
          conditionStatement = new CodeConditionStatement();
          if (type.SoftwareModel.Language != TargetLanguage.IsoCpp)
          {
            conditionStatement.Condition = new CodeBinaryOperatorExpression(lhsIdentifier, CodeBinaryOperatorType.IdentityEquality, rhsIdentifier);
          }
          else // C++ special case
          {
            conditionStatement.Condition = new CodeMethodInvokeExpression(lhsIdentifier, "Equals", rhsIdentifier);
          }
          conditionStatement.TrueStatements.Add(new CodeAssignStatement(fieldReference, integerOneReference));
          conditionStatement.FalseStatements.Add(new CodeAssignStatement(fieldReference, integerZeroReference));
          statements.Add(conditionStatement);
          break;
        case ExpressionType.Neq:
          conditionStatement = new CodeConditionStatement();
          if (type.SoftwareModel.Language != TargetLanguage.IsoCpp)
          {
            conditionStatement.Condition = new CodeBinaryOperatorExpression(lhsIdentifier, CodeBinaryOperatorType.IdentityInequality, rhsIdentifier);
          }
          else // C++ special case
          {
            conditionStatement.Condition = new CodeMethodInvokeExpression(lhsIdentifier, "NotEquals", rhsIdentifier);
          }
          conditionStatement.TrueStatements.Add(new CodeAssignStatement(fieldReference, integerOneReference));
          conditionStatement.FalseStatements.Add(new CodeAssignStatement(fieldReference, integerZeroReference));
          statements.Add(conditionStatement);
          break;
        default:
          throw new System.ApplicationException(string.Format("Invalid binary expression type: {0}", bExpr.ExpressionType));
      }

      return fieldReference;
    }
  }
}
